package com.unbiz.common.bean;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
import java.util.HashMap;  
import java.util.Map;

import com.unbiz.common.StringUtil;
import com.unbiz.common.cache.FieldCache;
import com.unbiz.common.cache.MethodCache;
  
public class ViewProxy implements InvocationHandler {  
	static MethodCache methodCache = MethodCache.getInstance();
	static FieldCache fieldCache = FieldCache.getInstance();
    private Object obj = null;  
   
    private Map<String, Method> methodMap = null;  
   
    public static <T> T as(Class<T> theInterface,Object obj) throws UnsupportedOperationException{  
    	InvocationHandler handler =  new ViewProxy(theInterface,obj);
    	Class<T> proxyClass = (Class<T>)Proxy.getProxyClass(theInterface.getClassLoader(), theInterface);
    	
    	T f = null;
		try {
			f = (T) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
		} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
				| NoSuchMethodException | SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	 
        return f;  
    } 
    
    private ViewProxy(Class<?> interfc, Object obj) {  
    	//Methods Map   	
       
        this.obj = obj; 
        this.methodMap = methodCache.getInstanceMethodsMap(obj.getClass()); 
    } 
    

    /**
     * Makes a descriptor for a given method.
     *
     * @param params    parameter types.
     * @param retType   return type.
     */
    public static String makeDescriptor(StringBuilder sbuf, Class<?>[] params, Class<?> retType) {      
        sbuf.append('(');
        for (int i = 0; i < params.length; i++)
            makeDesc(sbuf, params[i]);

        sbuf.append(')');
        if (retType != null)
            makeDesc(sbuf, retType);

        return sbuf.toString();
    }
    
    private static void makeDesc(StringBuilder sbuf, Class type) {
        if (type.isArray()) {
            sbuf.append('[');
            makeDesc(sbuf, type.getComponentType());
        }
        else if (type.isPrimitive()) {
            if (type == Void.TYPE)
                sbuf.append('V');
            else if (type == Integer.TYPE)
                sbuf.append('I');
            else if (type == Byte.TYPE)
                sbuf.append('B');
            else if (type == Long.TYPE)
                sbuf.append('J');
            else if (type == Double.TYPE)
                sbuf.append('D');
            else if (type == Float.TYPE)
                sbuf.append('F');
            else if (type == Character.TYPE)
                sbuf.append('C');
            else if (type == Short.TYPE)
                sbuf.append('S');
            else if (type == Boolean.TYPE)
                sbuf.append('Z');
            else
                throw new RuntimeException("bad type: " + type.getName());
        }
        else
            sbuf.append('L').append(type.getName().replace('.', '/'))
                .append(';');
    }
    
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable {  
        Object result = null;  
        String methodName = method.getName();  
        boolean zeroArgs = args == null || args.length == 0;
        
        String mangleName = MethodCache.getMethodSignature(methodName, method.getParameterTypes());
    	Method realMethod = methodMap.get(mangleName);
    	if(realMethod!=null){        		
    		result = realMethod.invoke(obj, args);    		
    	}
    	else{
	        if (methodName.equals("toString") && zeroArgs) {
	            return obj.toString();  
	        } 
	        else if (methodName.equals("hashCode") && zeroArgs){
	            return obj.hashCode();
	        }
	        else if (methodName.equals("equals") && ! zeroArgs){
	            return obj.equals(args[0]);
	        }
	        else if (methodName.startsWith("get")  && zeroArgs) {
	            String name = StringUtil.decapitalize(methodName.substring(3)); 
	            Field field = fieldCache.getInstanceField(obj.getClass(), name);
	            if(field!=null){
	            	result = field.get(obj);
	            }	            
	        }
	        else if (methodName.startsWith("set") && !zeroArgs) {  
	        	String name = StringUtil.decapitalize(methodName.substring(3));  
	        	Field field = fieldCache.getInstanceField(obj.getClass(), name);
	            if(field!=null){
	            	field.set(obj, args[0]);
	            }
	            return null;  
	        }
	        else if (methodName.equals("get")  && !zeroArgs) { //as Map interface
	        	String name = args[0].toString(); 
	            Field field = fieldCache.getInstanceField(obj.getClass(), name);
	            if(field!=null){
	            	result = field.get(obj);
	            }	            
	        }
	        else if (methodName.equals("put") && !zeroArgs) {  //as Map interface
	        	String name = args[0].toString();  
	        	Field field = fieldCache.getInstanceField(obj.getClass(), name);
	            if(field!=null){
	            	field.set(obj, args[1]);
	            }
	            return null;  
	        }
	        else if (methodName.startsWith("is") && zeroArgs) {  
	        	 String name = StringUtil.decapitalize(methodName.substring(2));  
	        	 Field field = fieldCache.getInstanceField(obj.getClass(), name);
	             if(field!=null){
	            	result = field.get(obj);
	             }
	        } 
	        else{
	        	throw new UnsupportedOperationException(methodName);
	        }
    	}
        return result;  
    }  
  
}  